package com.deluxelx.middleware.ratelimiter.valve.impl;

import com.alibaba.fastjson.JSON;
import com.deluxelx.middleware.ratelimiter.annotation.DoRateLimiter;
import com.deluxelx.middleware.ratelimiter.constants.Constants;
import com.deluxelx.middleware.ratelimiter.valve.IValveService;
import com.google.common.util.concurrent.RateLimiter;
import org.aspectj.lang.ProceedingJoinPoint;

import java.lang.reflect.Method;

public class RateLimiterValveImpl implements IValveService {

    private static RateLimiter rateLimiter = null;

    private static final Object LOCK = new Object();

    @Override
    public Object access(ProceedingJoinPoint jp, Method method, DoRateLimiter doRateLimiter, Object[] args) throws Throwable {
        // 判断是否开启限流
        if (0 == doRateLimiter.permitsPerSecond())
            return jp.proceed();

        String clazzName = jp.getTarget().getClass().getName();
        String methodName = method.getName();

        String key = clazzName + "." + methodName;
        rateLimiter = Constants.rateLimiterMap.get(key);

        RateLimiter rateLimiter = initRateLimiter(jp, method, doRateLimiter, key);
        // 获取一个令牌，如果获取成功，说明并未超过限流区间。获取失败，则超过最大限流区间。
        if (rateLimiter.tryAcquire()) {
            return jp.proceed();
        }

        return JSON.parseObject(doRateLimiter.returnJson(), method.getReturnType());
    }

    private static RateLimiter initRateLimiter(ProceedingJoinPoint jp, Method method, DoRateLimiter doRateLimiter, String key) {
        if (rateLimiter != null) {
            return rateLimiter;
        }
        synchronized (LOCK) {
            // 双检索单例化，针对每一个 key，确保不会重复的创建对象
            if (rateLimiter != null) {
                return rateLimiter;
            }
            createRateLimiter(jp, method, doRateLimiter, key);
            return rateLimiter;
        }
    }


    private static void createRateLimiter(ProceedingJoinPoint jp, Method method, DoRateLimiter doRateLimiter, String key) {
        // 经过双检索加锁单例化，rateLimiter 一定为 Null
        Constants.rateLimiterMap.put(key, RateLimiter.create(doRateLimiter.permitsPerSecond()));
        rateLimiter = Constants.rateLimiterMap.get(key);
    }

}
